-
Notifications
You must be signed in to change notification settings - Fork 117
feat: add the tab_footnote() method
#763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #763 +/- ##
==========================================
+ Coverage 91.61% 91.89% +0.27%
==========================================
Files 47 47
Lines 5773 6094 +321
==========================================
+ Hits 5289 5600 +311
- Misses 484 494 +10 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Can you resolve the conflicts on this? |
|
@machow Will do, thanks! |
Code reviewFound 3 issues:
The footnote location ordering values (
While investigation shows the stored (CLAUDE.md says "Use functools.partial - Reduce duplication for context-specific variants") great-tables/great_tables/_locations.py Lines 1206 to 1214 in 9eda034
great-tables/great_tables/_utils_render_html.py Lines 28 to 53 in 9eda034
Several location types are handled in
Additionally, line 49 checks for great-tables/great_tables/_utils_render_html.py Lines 28 to 53 in 9eda034
great-tables/great_tables/_locations.py Lines 1304 to 1322 in 9eda034
great-tables/great_tables/_locations.py Lines 1331 to 1357 in 9eda034
The This makes the stored great-tables/great_tables/_gt_data.py Lines 889 to 895 in 9eda034
🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
machow
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind making the adjustments to locnum mentioned in the comment?
Notes
From digging a bit w/ claude code, here's a bulleted footnote mark path.
from great_tables import GT, loc
from great_tables._utils_render_html import (
_get_footnote_mark_string,
_process_footnotes_for_display,
)
import pandas as pd
# Create a simple table
df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
# Apply the SAME footnote text to TWO different locations
gt = (
GT(df)
.tab_footnote(
footnote="This is a shared note",
locations=loc.body(columns="A", rows=[0])
)
.tab_footnote(
footnote="This is a shared note", # Same text!
locations=loc.body(columns="B", rows=[1])
)
)
# Build the data to resolve footnotes
built = gt._build_data("html")
# Show that both FootnoteInfo objects exist
print(f"Number of footnotes in _footnotes: {len(built._footnotes)}")
for i, fn in enumerate(built._footnotes):
print(f" [{i}] text={fn.footnotes[0]!r}, col={fn.colname}, row={fn.rownum}")
# Show that they get the SAME mark (deduplication by text)
print("\nMark assignments:")
for fn in built._footnotes:
mark = _get_footnote_mark_string(built, fn)
print(f" text={fn.footnotes[0]!r} -> mark={mark!r}")
# Show the processed footnotes for footer (deduplicated)
footer_footnotes = _process_footnotes_for_display(built, built._footnotes)
print(f"\nFootnotes in footer (deduplicated): {len(footer_footnotes)}")
for fn in footer_footnotes:
print(f" mark={fn['mark']!r}, text={fn['text']!r}")Footnote deduplication by text (same mark for identical text):
- tab_footnote() creates a FootnoteInfo object for each call, stored in GT._footnotes
- When rendering, _get_footnote_mark_string() (_utils_render_html.py:1155) determines marks:
a. Collects all footnotes with their positions (locnum, rownum, colnum)
b. Sorts by visual order (top-to-bottom, left-to-right)
c. Builds unique_footnotes list, deduplicating by text content (lines 1200-1204)
d. Looks up mark index via unique_footnotes.index(footnote_text) (line 1210) - For footer rendering, _process_footnotes_for_display() also deduplicates by text (line 1059: if footnote_text not in footnote_data)
- Result: Multiple tab_footnote() calls with identical text → same mark in all locations, single entry in footer
| updated_footnotes = [] | ||
| for spanner_id in new_loc.ids: | ||
| for footnote_info in new_footnotes: | ||
| updated_footnote = replace(footnote_info, locname=loc, grpname=spanner_id, locnum=3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From pairing, let's change blocks like this to not pass in locnum (which was hard-coded based off of this func dispatching on LocSpannerLabels). Let's do the following:
- Remove locnum attribute from the FootnoteEntry class def
- just use FootnoteEntry.locname.locnum (e.g. LocSpannerLabels.locnum)
Later may be useful to rename locname to loc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the automated review I think locnum might be unused anyways?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all the footnote updating logic across set_style functions is essentially the same (especially once manual locnum setting is removed)
machow
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind making the adjustments to locnum mentioned in the comment?
Notes
From digging a bit w/ claude code, here's a bulleted footnote mark path.
from great_tables import GT, loc
from great_tables._utils_render_html import (
_get_footnote_mark_string,
_process_footnotes_for_display,
)
import pandas as pd
# Create a simple table
df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
# Apply the SAME footnote text to TWO different locations
gt = (
GT(df)
.tab_footnote(
footnote="This is a shared note",
locations=loc.body(columns="A", rows=[0])
)
.tab_footnote(
footnote="This is a shared note", # Same text!
locations=loc.body(columns="B", rows=[1])
)
)
# Build the data to resolve footnotes
built = gt._build_data("html")
# Show that both FootnoteInfo objects exist
print(f"Number of footnotes in _footnotes: {len(built._footnotes)}")
for i, fn in enumerate(built._footnotes):
print(f" [{i}] text={fn.footnotes[0]!r}, col={fn.colname}, row={fn.rownum}")
# Show that they get the SAME mark (deduplication by text)
print("\nMark assignments:")
for fn in built._footnotes:
mark = _get_footnote_mark_string(built, fn)
print(f" text={fn.footnotes[0]!r} -> mark={mark!r}")
# Show the processed footnotes for footer (deduplicated)
footer_footnotes = _process_footnotes_for_display(built, built._footnotes)
print(f"\nFootnotes in footer (deduplicated): {len(footer_footnotes)}")
for fn in footer_footnotes:
print(f" mark={fn['mark']!r}, text={fn['text']!r}")Footnote deduplication by text (same mark for identical text):
- tab_footnote() creates a FootnoteInfo object for each call, stored in GT._footnotes
- When rendering, _get_footnote_mark_string() (_utils_render_html.py:1155) determines marks:
a. Collects all footnotes with their positions (locnum, rownum, colnum)
b. Sorts by visual order (top-to-bottom, left-to-right)
c. Builds unique_footnotes list, deduplicating by text content (lines 1200-1204)
d. Looks up mark index via unique_footnotes.index(footnote_text) (line 1210) - For footer rendering, _process_footnotes_for_display() also deduplicates by text (line 1059: if footnote_text not in footnote_data)
- Result: Multiple tab_footnote() calls with identical text → same mark in all locations, single entry in footer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind making the adjustments to locnum mentioned in the comment?
- remove locnum from footnote class
Notes
From digging a bit w/ claude code, here's a bulleted footnote mark path.
from great_tables import GT, loc
from great_tables._utils_render_html import (
_get_footnote_mark_string,
_process_footnotes_for_display,
)
import pandas as pd
# Create a simple table
df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
# Apply the SAME footnote text to TWO different locations
gt = (
GT(df)
.tab_footnote(
footnote="This is a shared note",
locations=loc.body(columns="A", rows=[0])
)
.tab_footnote(
footnote="This is a shared note", # Same text!
locations=loc.body(columns="B", rows=[1])
)
)
# Build the data to resolve footnotes
built = gt._build_data("html")
# Show that both FootnoteInfo objects exist
print(f"Number of footnotes in _footnotes: {len(built._footnotes)}")
for i, fn in enumerate(built._footnotes):
print(f" [{i}] text={fn.footnotes[0]!r}, col={fn.colname}, row={fn.rownum}")
# Show that they get the SAME mark (deduplication by text)
print("\nMark assignments:")
for fn in built._footnotes:
mark = _get_footnote_mark_string(built, fn)
print(f" text={fn.footnotes[0]!r} -> mark={mark!r}")
# Show the processed footnotes for footer (deduplicated)
footer_footnotes = _process_footnotes_for_display(built, built._footnotes)
print(f"\nFootnotes in footer (deduplicated): {len(footer_footnotes)}")
for fn in footer_footnotes:
print(f" mark={fn['mark']!r}, text={fn['text']!r}")Footnote deduplication by text (same mark for identical text):
- tab_footnote() creates a FootnoteInfo object for each call, stored in GT._footnotes
- When rendering, _get_footnote_mark_string() (_utils_render_html.py:1155) determines marks:
a. Collects all footnotes with their positions (locnum, rownum, colnum)
b. Sorts by visual order (top-to-bottom, left-to-right)
c. Builds unique_footnotes list, deduplicating by text content (lines 1200-1204)
d. Looks up mark index via unique_footnotes.index(footnote_text) (line 1210) - For footer rendering, _process_footnotes_for_display() also deduplicates by text (line 1059: if footnote_text not in footnote_data)
- Result: Multiple tab_footnote() calls with identical text → same mark in all locations, single entry in footer
This PR adds the
tab_footnote()method which allows you to add footnotes for different locations in the table. The method integrates footnote mark placement into the rendering pipeline for table headings, column labels, body cells, etc., and extends the internal data structures and options to support footnote config.Here's an example of how this works in practice:
And here is how it looks:
Fixes: #164